--			Settings			--
local progresscoloring = true	--true/false - Enable colorizing tooltip text based on progress.

if not LibStub then return error("gotQuests requires LibStub") end

--Declare some variables for use throughout the Addon.
local QuestList = {} --Stores all quest entries.

local gotQuests = CreateFrame("Frame", "gotQuests")

LibStub:GetLibrary("LibgQuest-1.0"):Embed(gotQuests)
gotQuests.events = {}

-- Constants
local QuestCompleted = ERR_QUEST_OBJECTIVE_COMPLETE_S
local ObjCompPattern = QuestCompleted:gsub("[()]", "%%%1"):gsub("%%s", "(%.%-)")

local WATCHFRAME_QUESTLINES = WATCHFRAME_QUESTLINES

--			Coloring functions			--
local colors = {}
colors.start = {1,0,0}		--Red color for no progress.
colors.halfway = {1,1,0}	--Yellow for halfway.
colors.done = {0,1,0}		--Green for done.

local function RGBPercToColorString(r,g,b)	--Convert r,g,b percentages to WoW colorstring.
	return string.format("|cff%02x%02x%02x", r*255, g*255, b*255)
end

local function CalculateGradient(percent)
	local output
	if percent == 0 then
		output = RGBPercToColorString(unpack(colors.start))
	elseif percent == 1 then
		output = RGBPercToColorString(unpack(colors.done))
	elseif percent == 0.5 then
		output = RGBPercToColorString(unpack(colors.halfway))
	elseif percent <= 0.5 then --Color should be between start and halfway.
		percent = percent / 0.5
		local r1,g1,b1 = unpack(colors.start)
		local r2,g2,b2 = unpack(colors.halfway)
		output = RGBPercToColorString(r1+(r2-r1)*percent, g1+(g2-g1)*percent, b1+(b2-b1)*percent)
	else
		percent = (percent-0.5) / 0.5
		local r1,g1,b1 = unpack(colors.halfway)
		local r2,g2,b2 = unpack(colors.done)
		output = RGBPercToColorString(r1+(r2-r1)*percent, g1+(g2-g1)*percent, b1+(b2-b1)*percent)
	end
	
	return output
end

local Gradient = setmetatable({}, {__index = function(t, k)
	local colorcode = CalculateGradient(k)
	t[k] = colorcode
	return colorcode
end})

-- Pimp the quest log a bit.
local DailyBlue = RGBPercToColorString(0.41, 0.8, 0.94)
local NormalYellow = RGBPercToColorString(1,1,0)
local function QuestLog_Update_Hook()
	local ScrollOffset = HybridScrollFrame_GetOffset(QuestLogScrollFrame)
	local buttons = QuestLogScrollFrame.buttons
 	local questLogTitle, questIndex, tag, questCheck
	local title, level, questTag, suggestedGroup, isHeader, isCollapsed, isComplete, isDaily
	local curText
	for i=1, #buttons do
		questIndex = i + ScrollOffset
		questLogTitle = buttons[i]
		tag = questLogTitle.tag
		questCheck = questLogTitle.check
		
		if questCheck:IsShown() then
			questCheck:ClearAllPoints()
			questCheck:SetPoint("LEFT", questLogTitle, "LEFT", 3, -2)
		end
		
		curText = questLogTitle:GetText()
		
		-- QuestLogScrollFrame.update can fire more than once per scroll action.
		-- If the first char of a questLogTitle's text is |, that means it's a color code we've created
		-- and the frame has already been updated.
		if curText and curText:sub(1,1) == "|" then
			return
		end
		if curText ~= "" then
			title, level, questTag, suggestedGroup, isHeader, isCollapsed, isComplete, isDaily = GetQuestLogTitle(questIndex)
			if isComplete then
				tag:Show()
			else
				tag:Hide()
			end
			if level and level > 0 then
				if questTag then
					questTag = questTag:sub(1,1):lower()
					if suggestedGroup and suggestedGroup > 0 then
						questTag = questTag..suggestedGroup
					end
				end
				questLogTitle:SetFormattedText("%s[%d%s]|r %s", isDaily and DailyBlue or NormalYellow, level, questTag or "", title)
			end
			
			QuestLogTitleButton_Resize(questLogTitle)
		end
	end
end

hooksecurefunc("QuestLog_Update", QuestLog_Update_Hook)

-- Even though QuestLogScrollFrame.update is set to QuestLog_Update,
-- the QuestLog_Update hook was not sufficient, so we're directly hooking
-- QuestLogScrollFrame.update as well.
hooksecurefunc(QuestLogScrollFrame, "update", QuestLog_Update_Hook)

--			Event management			--
gotQuests:SetScript("OnEvent", function(self, event, ...)
	local eventType = type(self.events[event])
	if eventType == "function" then
		self.events[event](self, event, ...)
	elseif eventType == "table" then
		for i, func in ipairs(self.events[event]) do
			func(self, event, ...)
		end
	end
end)

local RegisterEvent = gotQuests.RegisterEvent
function gotQuests:RegisterEvent(event, func)
	if not self.events[event] then
		self.events[event] = func
		RegisterEvent(self, event)
	elseif type(self.events[event]) == "function" then
		self.events[event] = {self.events[event], func}
	else
		table.insert(self.events[event], func)
	end
end

-- Set quest log selection to recently accepted quest.
gotQuests:RegisterEvent("QUEST_ACCEPTED",  function(self, event, index)
	if not QuestLogFrame:IsShown() and index then
		SelectQuestLogEntry(index)
	end
end)


-- Block UIErrorsFrame messages.
local UIErrorsFrame_OldOnEvent = UIErrorsFrame:GetScript("OnEvent")
UIErrorsFrame:SetScript("OnEvent", function(self, event, msg, ...)
	if event == "UI_INFO_MESSAGE" then
		if msg:find("(.-): (.-)/(.+)") or msg:find(ObjCompPattern) then
			return
		end
	end

	return UIErrorsFrame_OldOnEvent(self, event, msg, ...)
end)

hooksecurefunc("WatchFrame_Update", function()
	local line, text
	local objective, cur, goal, isComplete
	for i=1, #WATCHFRAME_QUESTLINES do
		line = WATCHFRAME_QUESTLINES[i]
		text = line.text:GetText()
		objective, cur, goal, isComplete = gotQuests:ParseObjectiveText(text)
		if objective then
			if tonumber(cur) then
				line.text:SetFormattedText("%s, %s%d/%d", objective, Gradient[cur/goal], cur, goal)
			end
		end
	end
	
	local questIndex
	local questLogTitleText, level, questTag, suggestedGroup, isHeader, isCollapsed, isComplete, isDaily 
	for watchIndex=1, GetNumQuestWatches() do
		local questIndex = GetQuestIndexForWatch(watchIndex)
		if questIndex then
			questLogTitleText, level, questTag, suggestedGroup, isHeader, isCollapsed, isComplete, isDaily = GetQuestLogTitle(questIndex)
			if isComplete then
				RemoveQuestWatch(questIndex)
			end
		end
	end
end)

-- Hook Logout() to add a check for completed dailies.
local _Logout = Logout
StaticPopupDialogs["COMPLETED_DAILIES_LOGOUT"] = {
	text = "You still have completed dailies that should be handed in before logging out. Are you sure you want to log out now?",
	button1 = YES,
	button2 = NO,
	OnAccept = _Logout,
	OnCancel = function (self, something, reason) if reason == "timeout" then _Logout() else self:Hide() end end,
	OnUpdate = function(self) local text = _G[self:GetName().."Text"] text:SetFormattedText("You still have completed dailies that should be handed in before logging out. Are you sure you want to log out now? %d seconds until logout.", self.timeleft) end,
	timeout = 20,
	whileDead = 1,
}

function Logout()
	local questLogTitleText, level, questTag, suggestedGroup, isHeader, isCollapsed, isComplete, isDaily 
	local completedDailies
	for questIndex = 1, 2*MAX_QUESTS do
		questLogTitleText, level, questTag, suggestedGroup, isHeader, isCollapsed, isComplete, isDaily = GetQuestLogTitle(questIndex)
		if isDaily and isComplete then
			completedDailies = true
			break
		end
	end
	
	if completedDailies then
		StaticPopup_Show("COMPLETED_DAILIES_LOGOUT")
	else
		_Logout()
	end
end

-- Redirect quest progress messages to RaidWarningFrame.
gotQuests:RegisterCallback("QUEST_OBJECTIVE_PROGRESS", function(event, questIndex, objective, cur, goal)
	if objective then
		local output
		if tonumber(cur) then
			cur = tonumber(cur)
			goal = tonumber(goal)
			output = string.format("%s: %s%d/%d", objective, Gradient[cur/goal], cur, goal)
		elseif cur and goal then
			output = string.format("%s: %s/%s", objective, cur, goal)
		end
		
		return RaidNotice_AddMessage(RaidWarningFrame, output, ChatTypeInfo["SYSTEM"])
	end
end)

gotQuests:RegisterCallback("QUEST_OBJECTIVE_COMPLETED", function(event, questIndex, objective)
	RaidNotice_AddMessage(RaidWarningFrame, QuestCompleted:format(objective), ChatTypeInfo["SYSTEM"])
	PlaySoundFile("Sound\\Creature\\Peon\\PeonReady1.wav")
end)

gotQuests:RegisterCallback("QUEST_COMPLETED", function(event, questIndex)
	RaidNotice_AddMessage(RaidWarningFrame, string.format(QuestCompleted, GetQuestLogTitle(questIndex)), ChatTypeInfo["SYSTEM"])
	PlaySoundFile("Sound\\Creature\\Peon\\PeonBuildingComplete1.wav")
	
	if IsQuestWatched(questIndex) then
		RemoveQuestWatch(questIndex)
		WatchFrame_Update()
	end
end)

-- And now for the tooltips!
GameTooltip:HookScript("OnShow", function(self)
	if not self.done then
		-- Prevent future updates of this tip.
		self.done = true
		
		local needLineShift
		local line, text
		local qData
		-- Iterate over the lines in the tooltip, starting at line 2 to skip the name.
		local lineIndex, numLines = 2, self:NumLines()
		while lineIndex <= numLines do
			line = _G["GameTooltipTextLeft"..lineIndex]
			if line then
				text = line:GetText()
				qData = gotQuests.Quests[text]
				if qData then
					-- Check if quest is complete.
					if qData.isComplete then
						line:SetText(nil)
						needLineShift = needLineShift and min(needLineShift, lineIndex) or lineIndex
						lineIndex = lineIndex + 1
						for i=lineIndex, numLines do
							line = _G["GameTooltipTextLeft"..i]
							if line:GetText():find("^ - ") then
								line:SetText(nil)
								lineIndex = lineIndex + 1
							else
								break
							end
						end
					else
						-- See if objective is complete.
						local header,headerIndex = line, lineIndex
						local nothidden
						for i=lineIndex+1, numLines do
							line = _G["GameTooltipTextLeft"..i]
							if line then
								text = line:GetText()
								if text and text:find("^ - ") then
									local objective, cur, goal, isComplete = gotQuests:ParseObjectiveText(text)
									if isComplete then
										line:SetText(nil)
										needLineShift = needLineShift and min(needLineShift, i) or i
									elseif objective then
										if tonumber(cur) then
											-- Progress color away!
											line:SetFormattedText("%s: %s%d/%d", objective, Gradient[cur/goal], cur, goal)
										end
										nothidden = true
									end
									lineIndex = lineIndex + 1
								else
									break
								end
							end
						end
						if not nothidden then
							header:SetText(nil)
							needLineShift = needLineShift and min(needLineShift, headerIndex) or headerIndex
						end
					end
				else
					lineIndex = lineIndex + 1
				end
			end
		end
		
		if needLineShift then
			local leftlines = {}
			local left
			for i=needLineShift, numLines do
				left = _G["GameTooltipTextLeft"..i]
				if left:GetText() then
					leftlines[#leftlines +1] = i
				end
			end
			
			local source, dest
			for i=1, #leftlines do
				dest = _G["GameTooltipTextLeft"..i+needLineShift-1]
				source = _G["GameTooltipTextLeft"..leftlines[i]]
				dest:SetText(source:GetText())
				dest:SetTextColor(source:GetTextColor())
				source:SetText(nil)
				dest:Show()
			end
		end
		
		local line
		for i=1, self:NumLines() do
			line = _G["GameTooltipTextLeft"..i]
			if not line:GetText() then
				line:Hide()
			end
		end
				
		-- Force resize.
		self:Show()
	end
end)

GameTooltip:HookScript("OnHide", function(self)
	self.done = nil
end)

GameTooltip:HookScript("OnTooltipSetUnit", function(self)
	self.done = nil
end)